from datetime import datetime
import time


import sqlalchemy as sa
from fastapi import Request
from sqlalchemy import insert, select, update

from forward.api.h5.schemas.user import UserAuthSchema, UserSchema
from forward.common.enums import SmsEnum, LoginClientEnum, WXOauthScopeEnum
from forward.common.sms_captcha import SmsCaptchaManager
from forward.dependencies.database import db
from forward.exceptions.base import AppException
from forward.api.h5.config import H5Config
from forward.api.h5.schemas.login import (
    FrontLoginCheckOutSchema,
    FrontRegisterInSchema,
)
from forward.http_base import HttpResp
from forward.model.tables.user import UserAuthModel, UserModel
from forward.utils.config import ConfigUtil
from forward.utils.redis import RedisUtil
from forward.utils.tools import ToolsUtil
from forward.utils.wechat import WeChatUtil
from wechatpy import WeChatOAuth


class LoginService:
    """
    登录管理服务实现类
    """

    select_columns = [
        UserModel.id,
        UserModel.username,
        UserModel.password,
        UserModel.salt,
        UserModel.mobile,
        UserModel.is_disable,
    ]

    def __init__(self, request: Request):
        self.request: Request = request

    async def get_code_url(self, redirect_uri: str):
        """获取微信小程序登录二维码"""
        scope = await ConfigUtil.get_val("login", "wxOauthScope")
        if not scope:
            scope = WXOauthScopeEnum.USERINFO.value
        wechat_oauth = WeChatOAuth(
            app_id=await ConfigUtil.get_val("oa_channel", "appId"),
            secret=await ConfigUtil.get_val("oa_channel", "appSecret"),
            redirect_uri=redirect_uri,
            scope=scope,
            state="123",
        )
        oauth_url = wechat_oauth.authorize_url
        return oauth_url

    async def make_login_token(
        self, user_id: int, mobile: str = ""
    ) -> FrontLoginCheckOutSchema:
        """
        生成登录Token
        :param user_id:
        :param mobile:
        :return:
        """
        token = ToolsUtil.make_token()
        token_valid_time = H5Config.token_valid_time
        redis_key = f"{H5Config.h5_token_key}{token}"
        await RedisUtil.set(redis_key, user_id, token_valid_time)
        return FrontLoginCheckOutSchema(
            user_id=user_id, is_band_mobile=bool(mobile != ""), token=token
        )

    async def query_user_by_username(self, username: str) -> UserSchema | None:
        if not username:
            return None
        user_record = await db.fetch_one(
            select(UserModel)
            .select_from(UserModel)
            .where(UserModel.is_delete == 0, UserModel.username == username)
        )
        if not user_record:
            return None
        return UserSchema.model_validate(user_record, from_attributes=True)

    async def query_user_by_mobile(self, mobile: int):
        if not mobile:
            return None
        user_record = await db.fetch_one(
            select(UserModel)
            .select_from(UserModel)
            .where(UserModel.is_delete == 0, UserModel.mobile == mobile)
        )
        return UserSchema.model_validate(user_record, from_attributes=True)

    async def query_user_by_uid(self, user_id: int) -> UserSchema | None:
        if not user_id:
            return None
        user_record = await db.fetch_one(
            select(UserModel)
            .select_from(UserModel)
            .where(UserModel.is_delete == 0, UserModel.id == user_id)
        )
        return UserSchema.model_validate(user_record, from_attributes=True)

    async def user_service(
        self, open_id: str, union_id: str, client: int
    ) -> FrontLoginCheckOutSchema:
        """
        用户创建服务
        :return:
        """
        user_auth_record = await db.fetch_one(
            select(UserAuthModel).where(
                sa.or_(
                    UserAuthModel.unionid == union_id, UserAuthModel.openid == open_id
                )
            )
        )
        user = None
        if user_auth_record:
            user_record = await db.fetch_one(
                select(UserModel)
                .select_from(UserModel)
                .where(
                    UserModel.is_delete == 0,
                    UserModel.id == UserAuthModel.user_id,
                )
            )
            user = UserSchema.model_validate(user_record, from_attributes=True)
        if not user:
            user_id = await self.insert_new_user()
            user_record = await db.fetch_one(
                select(UserModel)
                .select_from(UserModel)
                .where(UserModel.is_delete == 0, UserModel.id == user_id)
            )
            user = UserSchema.model_validate(user_record, from_attributes=True)
        if not user_auth_record:
            user_auth_record = await self.insert_new_user_auth(
                user.id, union_id, client, open_id
            )
        user_auth = UserAuthSchema.model_validate(
            user_auth_record, from_attributes=True
        )
        if not user_auth.unionid:
            await self.update_user_auth(user.id, unionid=union_id)

        ip = self.request.client.host if self.request.client else ""
        await self.update_user_info(user.id, ip=ip, login_time=datetime.now())

        return await self.make_login_token(user.id, user.mobile)

    async def update_user_auth(self, user_id, **kwargs):
        return await db.execute(
            update(UserAuthModel).where(UserModel.user_id == user_id).values(**kwargs)
        )

    async def insert_new_user_auth(self, user_id, union_id, client, open_id):
        user_auth_dict = {
            "user_id": user_id,
            "unionid": union_id or "0",
            "client": client.value,
            "openid": open_id,
        }
        return await db.execute(insert(UserAuthModel).values(**user_auth_dict))

    async def update_user_info(self, user_id: int, ip: str, login_time: datetime):
        update_dict = {}
        if ip:
            update_dict[UserModel.last_login_ip.name] = ip
        if login_time:
            update_dict[UserModel.last_login_time.name] = login_time
        if update_dict:
            return await db.execute(
                update(UserModel).where(UserModel.id == user_id).values(**update_dict)
            )

    async def rand_make_sn(self):
        """
        生成随机端用户编号
        :return:
        """
        while True:
            sn = ToolsUtil.random_int(8)
            user = await db.fetch_one(
                select(UserModel.id)
                .select_from(UserModel)
                .where(UserModel.is_delete == 0, UserModel.sn == sn)
            )
            if not user:
                break
        return sn

    async def insert_new_user(self):
        """
        插入一个新用户
        :return:
        """
        sn = await self.rand_make_sn()
        user_info = {
            "sn": sn,
            "nickname": "用户%s" % sn,
            "username": "u%s" % sn,
            "avatar": "/api/static/default_avatar.png",
            "channel": LoginClientEnum.PC.value,
            "last_login_time": datetime.now(),
            "last_login_ip": getattr(self.request.client, "host", ""),
        }
        return await db.execute(insert(UserModel).values(**user_info))

    async def register(self, param: FrontRegisterInSchema):
        """
        用户注册
        :param param:
        :return:
        """
        user = await self.query_user_by_username(username=param.username)
        assert not user, "账号已存在,换一个吧!"
        sn = await self.rand_make_sn()
        salt = ToolsUtil.random_string(5)
        md5_pwd = ToolsUtil.make_md5(f"{param.password}{salt}")

        user_info = {
            "sn": sn,
            "nickname": "用户%s" % sn,
            "username": param.username,
            "password": md5_pwd,
            "salt": salt,
            "avatar": "/api/static/default_avatar.png",
            "channel": param.client.value,
        }
        return await db.execute(insert(UserModel).values(**user_info))

    async def account_login(
        self, username: str, password: str
    ) -> FrontLoginCheckOutSchema:
        """
        登录管理 账号登录
        :param username:
        :param password:
        :return:
        """
        assert username, "username参数缺失"
        assert password, "password参数缺失"
        user = await self.query_user_by_username(username)
        assert user, "账号不存在"

        md5_pwd = ToolsUtil.make_md5(f"{password}{user.salt}")
        assert md5_pwd == user.password, "账号或密码错误"
        assert not user.is_disable, "账号已被禁用"
        ip = getattr(self.request.client, "host", "")
        await self.update_user_info(user.id, ip=ip, login_time=datetime.now())
        return await self.make_login_token(user_id=user.id, mobile=user.mobile)

    async def sms_login(self, mobile, code) -> FrontLoginCheckOutSchema:
        """
        手机号登录
        :return:
        """
        assert mobile, "mobile参数缺失"
        assert code, "code参数缺失"
        scene = SmsEnum.LOGIN
        sms_code = await SmsCaptchaManager.get_code(mobile, scene)
        assert sms_code and str(code) == str(sms_code), "验证码错误"

        await SmsCaptchaManager.del_code(mobile, scene)

        user = await self.query_user_by_mobile(mobile)

        assert user, "账号不存在"
        assert not user.is_disable, "账号已被禁用"
        ip = getattr(self.request.client, "host", "")
        await self.update_user_info(user.id, ip=ip, login_time=datetime.now())
        return await self.make_login_token(user_id=user.id, mobile=user.mobile)

    async def mnp_login(self, code, client) -> FrontLoginCheckOutSchema:
        """
        小程序登录
        :return:
        """
        wx_client = await WeChatUtil.mnp()
        try:
            session_result = wx_client.code_to_session(code)
        except Exception as e:
            raise AppException(HttpResp.FAILED, msg=str(e))
        open_id = session_result.get("openid")
        union_id = session_result.get("unionid")
        return await self.user_service(open_id, union_id, client=client)

    async def wechat_office(self, code, client) -> FrontLoginCheckOutSchema:
        """
        公众号登录
        :return:
        """
        print("code",code)
        scope = await ConfigUtil.get_val("login", "wxOauthScope")
        if not scope:
            scope = WXOauthScopeEnum.USERINFO.value
        wechat_oauth = WeChatOAuth(
            app_id=await ConfigUtil.get_val("oa_channel", "appId"),
            secret=await ConfigUtil.get_val("oa_channel", "appSecret"),
            redirect_uri="",
            scope=scope,
        )
        res = wechat_oauth.fetch_access_token(code)
        open_id = res.get("openid")
        union_id = res.get("unionid")
        access_token = res.get("access_token")
        print("wechat oa login", res)
        print("wechat oa login", open_id, union_id)
        # user_info = wechat_oauth.get_user_info()
        # print("user_info", user_info)
        return await self.user_service(open_id, union_id, client=client)

    @classmethod
    async def instance(cls, request: Request):
        """实例化"""
        return cls(request=request)
